// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu, jhkim

#include "coin2/exchange/sim/order_v1/subsystem.h"

#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <experimental/bits/fs_fwd.h>
#include <fmt/format.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/math/time_moving_window.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/di/constant.h"
#include "coin2/exchange/factory/order/system.h"
#include "coin2/exchange/sim/order_v1/gateway.h"
#include "coin2/exchange/symbology.pb.h"
#include "coin2/feed/archive/system.h"
#include "coin2/feed/mgr/system.h"
#include "coin2/strategy/linear_model/product_strategy.h"
#include "coin2/strategy/pta/pnl.h"
#include "presto/quant/symbology/symbol_map.h"

namespace coin2::exchange::sim::order_v1 {

using ::presto::Symbol;
using ::presto::SymbolManager;
using ::presto::quant::feature::OrderResponseProto;
using ::presto::quant::feature::OrderResponsesProto;
using ::presto::quant::feature::OrdersProto;
using ::presto::quant::feature::SingleResponseProto;

using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::feed::IFeedSubsystem;
using coin2::exchange::base::order::IOrderSubscriber;
using coin2::exchange::base::order::OrderContext;
using coin2::exchange::base::order::OrderContextManager;
using coin2::exchange::base::order::OrderUpdateManager;
using coin2::exchange::base::strategy_util::LatencyRecorder;
using coin2::exchange::order::OrderSystemConfig;
using coin2::exchange::symbology::ProductRequest;

using coin::proto::OrderDirection;
using coin::proto::OrderDuration;
using coin::proto::OrderEvent;
using coin::proto::OrderType;
using coin::proto::TradeSide;

using coin2::exchange::order::SimConfig;

using coin2::exchange::base::order::IOrderSubscriber;
using coin2::exchange::order::OrderSystemConfig;

namespace sugar {

SimConfig2 GetSimConfig2(const OrderSystemConfig& oss_config) {
  SimConfig2 ret;
  if (oss_config.has_sim_config2()) {
    ret.CopyFrom(oss_config.sim_config2());
  } else {
    LOG(WARNING) << "FROM sim_config2";
    CHECK(oss_config.has_sim_config());
    for (int i = 0; i < oss_config.sim_config().reserve_symbols_size(); ++i) {
      ProductRequest req;
      req.set_mea(oss_config.mea());
      req.set_norm(oss_config.sim_config().reserve_symbols(i));
      for (auto&& product:
          ResolveProductRequestFromPotentiallyWildcard(
              req,
              oss_config.sim_config().start_timestamp(),
              false)) {
        auto* acct = ret.add_accounts();
        acct->set_sim_account_id(0L);
        acct->set_relative_norm(product->relative_norm());
        if (oss_config.sim_config().initial_positions_size() > i) {
          acct->set_initial_position(oss_config.sim_config().initial_positions(i));
        }
        if (oss_config.sim_config().reserve_positions_size() > i) {
          acct->set_reserve_position(oss_config.sim_config().reserve_positions(i));
        }
        if (oss_config.sim_config().has_end_timestamp()) {
          acct->set_end_timestamp(oss_config.sim_config().end_timestamp());
        } else {
          acct->set_end_timestamp(0);
        }
        acct->set_start_timestamp(oss_config.sim_config().start_timestamp());
        acct->set_is_snapshot(oss_config.sim_config().is_snapshot());
        acct->set_latency_sec(oss_config.sim_config().latency_sec());
        acct->set_cancel_latency_sec(oss_config.sim_config().cancel_latency_sec());
        acct->set_taker_rivals_mabboqty_mult(oss_config.sim_config().taker_rivals_mabboqty_mult());
        acct->set_makerfill_tolerance_bps(oss_config.sim_config().makerfill_tolerance_bps());
        acct->set_use_exchange_timestamp(oss_config.sim_config().use_exchange_timestamp());
        switch (oss_config.sim_config().result_format()) {
          case SimConfig::RESULT_NONE:
            acct->set_result_format(AccountSimConfig::RESULT_NONE);
            break;
          case SimConfig::RESULT_FORMAT_PNL_JSON_PRINT:
            acct->set_result_format(AccountSimConfig::RESULT_FORMAT_PNL_JSON_PRINT);
            break;
          case SimConfig::RESULT_FORMAT_PROTO_FILE_DUMP:
            acct->set_result_format(AccountSimConfig::RESULT_FORMAT_PROTO_FILE_DUMP);
            break;
          case SimConfig::RESULT_FORMAT_PNL_JSON_PRINT_AND_PROTO_DUMP:
            acct->set_result_format(AccountSimConfig::RESULT_FORMAT_PNL_JSON_PRINT_AND_PROTO_DUMP);
            break;
          default:
            NOTREACHED() << oss_config.sim_config().result_format();
        }
        {
          std::experimental::filesystem::path result_path =
              oss_config.sim_config().sim_result_filepath();
          std::experimental::filesystem::path filename = result_path.stem();
          filename += acct->relative_norm();
          filename += oss_config.mea();
          filename += result_path.extension();
          result_path.replace_filename(filename);
          acct->set_sim_result_filepath(result_path.string());
          LOG(INFO) << result_path.string();
        }
      }
    }
  }
  return ret;
}

}  // namespace sugar


bool MeaMatches(
    const MarketExchangeApi& mea1,
    const MarketExchangeApi& mea2) {
  return (
      mea1 == mea2 ||
      (
          mea1.market == mea2.market &&
          mea1.exchange == mea2.exchange &&
          (
              (mea1.api == "v5-swap" && mea2.api == "v3-swap") ||
              (mea1.api == "v3-swap" && mea2.api == "v5-swap"))));
}

SimSubsystem::SimSubsystem(
    const std::string& name,
    const MarketExchangeApi& mea,
    const di::ConstantInjection& ci,
    const OrderSystemConfig& oss_config,
    base::executor::ArchiveSyncExecutor* exec,
    IOrderSubscriber* callback,
    const coin2::exchange::order::PrivateSubscriberConfig& subscriber_config,
    GetProductHolderFunc get_product_holder)
    : BaseOrderSubsystem(name, oss_config, callback) {
  SetOsFeature(mea);
  account_request_.set_market_type(base::market::MarketTypeToString(mea.market));
  account_request_.set_exchange(base::market::ExchangeTypeToString(mea.exchange));
  account_request_.set_owner("sim");

  get_product_holder_ = get_product_holder;
  symcache_.set_mea(mea);
  symcache_.SetGetProductHolder(get_product_holder);
  bts_ = std::make_unique<BacktestingGateway>(
          name,
          mea,
          oss_config,
          sugar::GetSimConfig2(oss_config),
          subscriber_config,
          &products_,
          &symcache_,
          callback);
  bts_->mutable_upd_mgr()->GetAccountInfoMutable()->set_owner("sim");
}

SimSubsystem::~SimSubsystem() {}

coin2::exchange::base::order::BaseOrderGateway* SimSubsystem::gateway() { return bts_.get(); }

coin2::exchange::base::order::BaseOrderProcessor& SimSubsystem::processor() { return *bts_.get(); }

void SimSubsystem::ConnectFeed(coin2::feed::mgr::FeedSubsystemManager* fsm) {
  std::vector<IFeedSubsystem*> fss_list = fsm->GetAllFeedSubsystems();
  for (IFeedSubsystem* fss : fss_list) {
    if (MeaMatches(fss->mea(), bts_->mea())) {
      auto* bfss = dynamic_cast<exchange::base::feed::FeedArchiveMixin*>(fss);
      if (bfss) {
        bfss->PrependCallback(bts_.get());
      }
    }
  }
}

coin2::exchange::base::order::OrderQuerier* SimSubsystem::GenOrderQuerierForSim(
    int64_t sim_account_id) {
  return bts_->GenOrderQuerier(sim_account_id);
}

}  // namespace coin2::exchange::sim::order_v1
